﻿using System.Linq.Expressions;

public enum ExpressionCombineType
{
    AndAlso,
    OrElse
}

public enum FormFieldType
{
    Int32,
    String,
}

public enum ConditionType
{
    Equal,
    NotEqual,
    GreaterThan,
    GreaterThanOrEqual,
    LessThan,
    LessThanOrEqual,
}

public class FormFieldInfo
{
    public string Name { get; set; }

    public FormFieldType Type { get; set; }

    public object Value { get; set; }

    public FormFieldInfo(string name, FormFieldType fieldType, object value)
    {
        Name = name;
        Type = fieldType;
        Value = value;
    }
}

public class ConditionInfo
{
    public FormFieldInfo Left { get; set; }

    public FormFieldInfo Right { get; set; }

    public ConditionType ConditionType { get; set; }
}

public static class ExpressionHelper
{
    public static Type GetType(FormFieldType fieldType)
    {
        Type type;

        switch (fieldType)
        {
            case FormFieldType.Int32:
                type = typeof(Int32);
                break;
            case FormFieldType.String:
                type = typeof(String);
                break;
            default:
                throw new ArgumentException($"Undefined type {fieldType.ToString()}");
        }

        return type;
    }

    public static bool Condition(FormFieldInfo left, FormFieldInfo right, ConditionType conditionType = ConditionType.Equal)
    {
        var func = GetConditionExpression(left, right, conditionType).Compile();
        return func.Invoke();
    }

    public static Expression<Func<bool>> GetConditionExpression(FormFieldInfo left, FormFieldInfo right, ConditionType conditionType = ConditionType.Equal)
    {
        var leftValue = Convert.ChangeType(left.Value, GetType(left.Type));
        var rightValue = Convert.ChangeType(right.Value, GetType(right.Type));

        var leftParameter = Expression.Constant(leftValue);
        var rightParameter = Expression.Constant(rightValue);

        Expression expression = conditionType switch
        {
            ConditionType.Equal => Expression.Equal(leftParameter, rightParameter),
            ConditionType.NotEqual => Expression.NotEqual(leftParameter, rightParameter),
            ConditionType.GreaterThan => Expression.GreaterThan(leftParameter, rightParameter),
            ConditionType.GreaterThanOrEqual => Expression.GreaterThanOrEqual(leftParameter, rightParameter),
            ConditionType.LessThan => Expression.LessThan(leftParameter, rightParameter),
            ConditionType.LessThanOrEqual => Expression.LessThanOrEqual(leftParameter, rightParameter),
            _ => throw new NotImplementedException($"{conditionType}")
        };

        return Expression.Lambda<Func<bool>>(expression);
    }

    public static Expression<Func<bool>> GetConditionExpression(ConditionInfo conditionInfo)
    {
        return GetConditionExpression(conditionInfo.Left, conditionInfo.Right, conditionInfo.ConditionType);
    }

    //public static Expression<Func<bool>> Combine(List<ConditionInfo> conditionInfos, ExpressionCombineType combineType = ExpressionCombineType.AndAlso)
    //{
    //    var expressions = conditionInfos.Select(c => GetConditionExpression(c)).ToList();
    //}

    public static Expression<Func<T, bool>>? Combine<T>(Expression<Func<T, bool>>? expression1, Expression<Func<T, bool>>? expression2, ExpressionCombineType combineType = ExpressionCombineType.AndAlso)
    {
        var parameter = Expression.Parameter(typeof(T));

        if (expression1 == null) return expression2;
        if (expression2 == null) return expression1;

        var leftVisitor = new ReplaceExpressionVisitor(expression1.Parameters[0], parameter);
        var left = leftVisitor.Visit(expression1.Body);
        if (left == null) throw new ArgumentNullException(nameof(left));

        var rightVisitor = new ReplaceExpressionVisitor(expression2.Parameters[0], parameter);
        var right = rightVisitor.Visit(expression2.Body);
        if (right == null) throw new ArgumentNullException(nameof(right));

        return combineType switch
        {
            ExpressionCombineType.AndAlso => Expression.Lambda<Func<T, bool>>(Expression.AndAlso(left, right), parameter),
            ExpressionCombineType.OrElse => Expression.Lambda<Func<T, bool>>(Expression.OrElse(left, right), parameter),
            _ => throw new NotImplementedException($"{combineType}"),
        };
    }

    class ReplaceExpressionVisitor : ExpressionVisitor
    {
        private readonly Expression _oldValue;
        private readonly Expression _newValue;

        public ReplaceExpressionVisitor(Expression oldValue, Expression newValue)
        {
            _oldValue = oldValue;
            _newValue = newValue;
        }

        public override Expression? Visit(Expression? node)
        {
            if (node == _oldValue)
            {
                return _newValue;
            }

            return base.Visit(node);
        }
    }
}
